<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="x-ua-compatible" content="ie=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="favicon.ico" type="image/x-icon"/>
  <link rel="stylesheet" href="examples-styles.css"/>

	<title>abcjs: Synth Demo</title>

	<link rel="stylesheet" type="text/css" href="../abcjs-audio.css">
	<style>
		main {
			max-width: 770px;
			margin: 0 auto;
		}
		.feedback {
			height: 600px;
			font-family: Arial, "sans-serif";
		}
		.highlight {
			fill: #0a9ecc;
		}
		.abcjs-cursor {
			stroke: red;
		}
		.click-explanation {
			color: red;
			font-style: italic;
		}
		.beat {
			font-weight: bold;
		}
		.label {
			color: #888888;
    }
		.midi {
			margin-top: 20px;
			margin-left: 5px;
		}
		.seek-controls {
			margin-top: 5px;
		}
		.seek-controls.disabled {
			background-color: #cccccc;
			opacity: 0.5;
		}
	</style>

	<script src="../dist/abcjs-basic.js" type="text/javascript"></script>
	<script type="text/javascript">

		function CursorControl() {
			var self = this;

			self.onReady = function() {
				var downloadLink = document.querySelector(".download");
				downloadLink.addEventListener("click", download);
				downloadLink.setAttribute("style", "");
				var clickEl = document.querySelector(".click-explanation")
				clickEl.setAttribute("style", "");
			};
			self.onStart = function() {
				var svg = document.querySelector("#paper svg");
				var cursor = document.createElementNS("http://www.w3.org/2000/svg", "line");
				cursor.setAttribute("class", "abcjs-cursor");
				cursor.setAttributeNS(null, 'x1', 0);
				cursor.setAttributeNS(null, 'y1', 0);
				cursor.setAttributeNS(null, 'x2', 0);
				cursor.setAttributeNS(null, 'y2', 0);
				svg.appendChild(cursor);

			};
			self.beatSubdivisions = 2;
			self.onBeat = function(beatNumber, totalBeats, totalTime) {
				if (!self.beatDiv)
					self.beatDiv = document.querySelector(".beat");
				self.beatDiv.innerText = "Beat: " + beatNumber + " Total: " + totalBeats + " Total time: " + totalTime;
			};
			self.onEvent = function(ev) {
				if (ev.measureStart && ev.left === null)
					return; // this was the second part of a tie across a measure line. Just ignore it.

				var lastSelection = document.querySelectorAll("#paper svg .highlight");
				for (var k = 0; k < lastSelection.length; k++)
					lastSelection[k].classList.remove("highlight");

				var el = document.querySelector(".feedback").innerHTML = "<div class='label'>Current Note:</div>" + JSON.stringify(ev, null, 4);
				for (var i = 0; i < ev.elements.length; i++ ) {
					var note = ev.elements[i];
					for (var j = 0; j < note.length; j++) {
						note[j].classList.add("highlight");
					}
				}

				var cursor = document.querySelector("#paper svg .abcjs-cursor");
				if (cursor) {
					cursor.setAttribute("x1", ev.left - 2);
					cursor.setAttribute("x2", ev.left - 2);
					cursor.setAttribute("y1", ev.top);
					cursor.setAttribute("y2", ev.top + ev.height);
				}
			};
			self.onFinished = function() {
				var els = document.querySelectorAll("svg .highlight");
				for (var i = 0; i < els.length; i++ ) {
					els[i].classList.remove("highlight");
				}
				var cursor = document.querySelector("#paper svg .abcjs-cursor");
				if (cursor) {
					cursor.setAttribute("x1", 0);
					cursor.setAttribute("x2", 0);
					cursor.setAttribute("y1", 0);
					cursor.setAttribute("y2", 0);
				}
			};
		}

		var cursorControl = new CursorControl();

		var abc = [
			"T: Cooley's\n" +
			"M: 4/4\n" +
			"Q: 1/4=120\n" +
			"L: 1/8\n" +
			"R: reel\n" +
			"K: Emin\n" +
			"|:{E}D2|EB{c}BA B2 EB|~B2 AB dBAG|FDAD BDAD|FDAD dAFD|\n" +
			"EBBA B2 EB|B2 AB defg|afe^c dBAF|DEFD E2:|\n" +
			"|:gf|eB B2 efge|eB B2 gedB|A2 FA DAFA|A2 FA defg|\n" +
			"eB B2 eBgB|eB B2 defg|afe^c dBAF|DEFD E2:|",

			"X:1\n" +
			"T:Bill Bailey\n" +
			"M:4/4\n" +
			"L:1/4\n" +
			"Q:1/4=210\n" +
			"K:C\n" +
			"\"C\"GA2c|e3/2^d/2eg|GA2c|e4|GA2c|e2g2|\"G7\"(gB3-|B4)|\n" +
			"GB2d|fefg|GB2d|f4|GB2d|g2\"G+\"a2|\"C\"(ae3-|e4)|\n" +
			"GA2c|e3/2^d/2eg|GA2c|e3G|GGce|g2_b2|\"F\"a2-a2-|a3c|\n" +
			"cc2c|\"F#dim7\"d2c2|\"C\"gg2a|\"A7\"e3e|\"D7\"ed^cd|\"G7\"f2e2|\"C\"c4-|czz2|]",

			"X:1\n" +
			"T:All Notes On Piano\n" +
			"M:4/4\n" +
			"Q:120\n" +
			"L:1/4\n" +
			"K:C clef=bass\n" +
			"A,,,,^A,,,,B,,,,C,,,|^C,,,D,,,^D,,,E,,,|F,,,^F,,,G,,,^G,,,|A,,,^A,,,B,,,C,,|\n" +
			"^C,,D,,^D,,E,,|F,,^F,,G,,^G,,|A,,^A,,B,,C,|^C,D,^D,E,|\n" +
			"K:C clef=treble\n" +
			"F,^F,G,^G,|A,^A,B,C|^CD^DE|F^FG^G|\n" +
			"A^ABc|^cd^de|f^fg^g|a^abc'|\n" +
			"^c'd'^d'e'|f'^f'g'^g'|a'^a'b'c''|^c''d''^d''e''|\n" +
			"f''^f''g''^g''|a''^a''b''c'''|^c'''4|]"
		];

		var tuneNames = [ "Cooleys", "Bill Bailey", "All Notes On Piano" ];

		var currentTune = 0;

		var synthControl;

		function clickListener(abcElem, tuneNumber, classes, analysis, drag, mouseEvent) {
			var output = "currentTrackMilliseconds: " + abcElem.currentTrackMilliseconds + "<br>" +
				"currentTrackWholeNotes: " + abcElem.currentTrackWholeNotes + "<br>" +
				"midiPitches: " + JSON.stringify(abcElem.midiPitches, null, 4) + "<br>" +
				"gracenotes: " + JSON.stringify(abcElem.gracenotes, null, 4) + "<br>" +
				"midiGraceNotePitches: " + JSON.stringify(abcElem.midiGraceNotePitches, null, 4) + "<br>";
			document.querySelector(".clicked-info").innerHTML = "<div class='label'>Clicked info:</div>" +output;

			var lastClicked = abcElem.midiPitches;
			if (!lastClicked)
				return;

			ABCJS.synth.playEvent(lastClicked, abcElem.midiGraceNotePitches, synthControl.visualObj.millisecondsPerMeasure()).then(function (response) {
				console.log("note played");
			}).catch(function (error) {
				console.log("error playing note", error);
			});
		}

		var abcOptions = {
			add_classes: true,
			clickListener: self.clickListener,
			responsive: "resize"
		};

		function load() {
			document.querySelector(".next").addEventListener("click", next);
			document.querySelector(".start").addEventListener("click", start);
			document.querySelector(".warp").addEventListener("click", warp);
			document.querySelector(".seek").addEventListener("click", seek);
			document.querySelector(".seek2").addEventListener("click", seek2);
			document.querySelector("#seek-units").addEventListener("change", seekExplanation);

			if (ABCJS.synth.supportsAudio()) {
				synthControl = new ABCJS.synth.SynthController();
				synthControl.load("#audio", cursorControl, {displayLoop: true, displayRestart: true, displayPlay: true, displayProgress: true, displayWarp: true});
			} else {
				document.querySelector("#audio").innerHTML = "<div class='audio-error'>Audio is not supported in this browser.</div>";
			}
			setTune(false);
		}

		function download() {
			if (synthControl)
				synthControl.download(tuneNames[currentTune] + ".wav");
		}

		function start() {
			if (synthControl)
				synthControl.play();
		}

		function seek() {
			synthControl.seek(0.50)
		}

		function seekExplanation() {
			var explanation = document.getElementById("unit-explanation");
			if (!synthControl.visualObj.noteTimings) {
				explanation.innerText = "First start playing to load audio before seeking.";
				return;
			}
			var units = this.value;
			var max = 1;
			switch (units) {
				case "seconds":
					max = synthControl.visualObj.getTotalTime();
					break;
				case "beats":
					max = synthControl.visualObj.getTotalBeats();
					break;
			}
			explanation.innerText = "Enter a number between 0 and {0}.".replace("{0}", max);
		}

		function seek2() {
			var amount = document.getElementById("seek-amount").value;
			var units = document.getElementById("seek-units").value;
			synthControl.seek(amount, units)
		}

		function warp() {
			var el = document.querySelector(".warp");
			el.setAttribute("disabled", true)
			var amount = Math.random()
			console.log("warp", amount)
			synthControl.setWarp(amount*100).then(function () {
				el.removeAttribute("disabled")
			})
		}

		function setTune(userAction) {
			var seekControls = document.querySelector(".seek-controls");
			seekControls.classList.add("disabled");
			synthControl.disable(true);
			var visualObj = ABCJS.renderAbc("paper", abc[currentTune], abcOptions)[0];
			var midi = ABCJS.synth.getMidiFile(abc[currentTune]);
			var midiButton = document.querySelector(".midi");
			midiButton.innerHTML = midi;

			// TODO-PER: This will allow the callback function to have access to timing info - this should be incorporated into the render at some point.
			var midiBuffer = new ABCJS.synth.CreateSynth();
			midiBuffer.init({
				//audioContext: new AudioContext(),
				visualObj: visualObj,
				// sequence: [],
				// millisecondsPerMeasure: 1000,
				// debugCallback: function(message) { console.log(message) },
				options: {
					// soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/" ,
					// sequenceCallback: function(noteMapTracks, callbackContext) { return noteMapTracks; },
					// callbackContext: this,
					// onEnded: function(callbackContext),
					// pan: [ -0.5, 0.5 ]
				}
			}).then(function (response) {
				console.log(response);
				if (synthControl) {
					synthControl.setTune(visualObj, userAction).then(function (response) {
						console.log("Audio successfully loaded.")
						seekControls.classList.remove("disabled");
						seekExplanation();
					}).catch(function (error) {
						console.warn("Audio problem:", error);
					});
				}
			}).catch(function (error) {
				console.warn("Audio problem:", error);
			});
		}

		function next() {
			currentTune++;
			if (currentTune >= abc.length)
				currentTune = 0;
			setTune(true);
		}


	</script>
</head>
<body onload="load()">
  <header>
    <img src="https://paulrosen.github.io/abcjs/img/abcjs_comp_extended_08.svg" alt="abcjs logo">
    <h1>Full Synth</h1>
  </header>
  <div class="container">
    <main>
      <p>This is a complete demo of what you can do with matching audio with the visual music.</p>
      <p>Different pieces of music are provided to demonstrate changing tunes. To toggle between the pieces, click "Next Tune".</p>
      <p>You are in control of the look of the cursor. Two different techniques are demonstrated: highlighting the note being played and putting a cursor on the page. The class CursorControl must be supplied by your program.</p>
      <p>As the piece is playing, there are callbacks when the note changes. The info returned in the callback is printed to the page as it is received.</p>
      <p>The visual control for playing music can look different. In this example, the abcjs-audio.css file has been loaded. You can supply your own css.</p>
      <button class="next">Next Tune</button>
      <button class="start">Start/Pause</button>
      <button class="warp">Rand. Warp</button>
      <button class="seek">Seek to Middle</button>
		<button class="download" style="display: none;">Download</button>
		<div class="seek-controls disabled">
			<label>Seek: <input type="number" min="0" id="seek-amount"></label>
			<select aria-label="seek units" id="seek-units">
				<option value="percent">Percent</option>
				<option value="seconds">Seconds</option>
				<option value="beats">Beats</option>
			</select>
			<button class="seek2">Seek</button>
			<span id="unit-explanation" class="click-explanation">First start playing to load audio before seeking.</span>
		</div>
      <div class="midi">MIDI</div>
      <div id="paper"></div>
      <div id="audio"></div>
      <p class="beat"></p>
      <p class="click-explanation" style="display:none;">Click on a note to play that note.</p>
      <pre class="clicked-info"></pre>
      <pre class="feedback"></pre>
    </main>
  </div>
</body>
</html>
